/* 
    sample code for H264 for pattern 720x480 input and bitstream output
    This sample code is to do encode 1000 stream frames named "/tmp/dev0.264"
    #./h264_main test.yuv
 */
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/videodev.h>
#include <errno.h>
#include <linux/fb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/un.h>
#include <signal.h>
#include <sys/time.h>

#include "favc_avcodec.h"
#include "ratecontrol.h"

//#define RATE_CTL
#define TEST_ROUND  100
#define TEST_WIDTH  704//1920
#define TEST_HEIGHT 480//1088

#define FIX_QUANT  32

#define FAVC_ENCODER_DEV  "/dev/f264enc"
#define dout_name         "/tmp/dev0.264"

typedef struct AVFrame {
    uint8_t *data[4];
} AVFrame;


typedef struct video_profile
{
    unsigned int bit_rate;
    unsigned int width;   //length per dma buffer
    unsigned int height;
    unsigned int framerate;
    unsigned int frame_rate_base;
    unsigned int gop_size;
    unsigned int qmax;
    unsigned int qmin;   
    unsigned int quant;
    unsigned int intra;
    AVFrame *coded_frame;
    char *priv;
} video_profile;


FILE            *din;
FILE            *dout;
int             favc_enc_fd=0;
H264_RateControl h264_ratec;
int             enc_mmap_addr;
unsigned int    out_virt_buffer;


typedef unsigned long long uint64;

struct timeval t1;
uint64  nsecs;
void time_start(struct timeval *tv)
{
    gettimeofday (tv, NULL);
}

uint64 time_delta(struct timeval *start, struct timeval *stop)
{
    uint64 secs, usecs;
    
    secs = stop->tv_sec - start->tv_sec;
    usecs = stop->tv_usec - start->tv_usec;
    if (usecs < 0)
    {
        secs--;
        usecs += 1000000;
    }
    return secs * 1000000 + usecs;
}

uint64 time_stop(struct timeval *tv)
{
    struct timeval end;
    gettimeofday (&end, NULL);
    return time_delta (tv, &end);
}


int h264_init(video_profile *video_setting)
{
    FAVC_ENC_PARAM     enc_param;
    int                 err;


    if(favc_enc_fd==0)
      favc_enc_fd=open(FAVC_ENCODER_DEV,O_RDWR);
	
    if(favc_enc_fd == 0)
    {
	    printf("Fail to open %s\n",FAVC_ENCODER_DEV);
	    return -1;
    }    

    enc_mmap_addr=(int)mmap(0,(video_setting->width*video_setting->height*3*2)/2,
        PROT_READ|PROT_WRITE,MAP_SHARED,favc_enc_fd, 0);
    if(enc_mmap_addr<=0)
        return -1;
    printf("mmap addr=0x%x\n",enc_mmap_addr);
    fflush(stdout);

    enc_param.u32FrameWidth=video_setting->width;
    enc_param.u32FrameHeight=video_setting->height;
    enc_param.fFrameRate = video_setting->framerate;
    enc_param.u32IPInterval = video_setting->gop_size;
    enc_param.u32MaxQuant       =video_setting->qmax;
    enc_param.u32MinQuant       =video_setting->qmin;
    enc_param.u32Quant = video_setting->quant;
    enc_param.u32BitRate = video_setting->bit_rate*1000;
    
#ifdef RATE_CTL
    H264RateControlInit(&h264_ratec,(float)enc_param.u32BitRate,(float)enc_param.fFrameRate,
        video_setting->width,video_setting->height,enc_param.u32IPInterval,enc_param.u32MaxQuant,enc_param.u32MinQuant,
        enc_param.u32Quant);
#endif

    err = ioctl(favc_enc_fd,FAVC_IOCTL_ENCODE_INIT,&enc_param);
    if(err < 0)
    {
    	close(favc_enc_fd);
	    printf("Error to set FAVC_IOCTL_INIT\n");
	    return -1;
    }
    
	return 0;
}



int enc_close(video_profile *video_setting)
{
	if(favc_enc_fd)
		close(favc_enc_fd);
	favc_enc_fd = 0;
	return 0;
}

int favc_encode(video_profile *video_setting, unsigned char *frame, void * data)
{
    AVFrame             *pict=(AVFrame *)data;
    FAVC_ENC_PARAM     enc_param;

    enc_param.pu8YFrameBaseAddr = (unsigned char *)pict->data[0];   //input user continued virtual address (Y), Y=0 when NVOP
    enc_param.pu8UFrameBaseAddr = (unsigned char *)pict->data[1];   //input user continued virtual address (U)
    enc_param.pu8VFrameBaseAddr = (unsigned char *)pict->data[2];   //input user continued virtual address (V)
    enc_param.bitstream = frame;  //output User Virtual address   

#ifdef RATE_CTL
    enc_param.u32Quant=H264RateControlGetQuant(&h264_ratec);   
#else
    enc_param.u32Quant=FIX_QUANT; //no rate control
#endif

    ioctl(favc_enc_fd,FAVC_IOCTL_ENCODE_FRAME,&enc_param);

#ifdef RATE_CTL
    H264RateControlUpdate(&h264_ratec,enc_param.bitstream_size,enc_param.frame_cost);
#endif

	video_setting->intra = enc_param.keyframe;

    return enc_param.bitstream_size;
}


int main(int argc, char **argv)
{
    int i;
    int length,fcount=0,tlength;
    video_profile   video_setting;
    AVFrame pict;
    
    dout=fopen(dout_name,"wb");
    printf("Use encoder output name %s\n",dout_name);

    if(argc<2)
        exit(0);
    printf("Input File Name %s\n",argv[1]);
    din=fopen(argv[1],"rb");
    
    //set the default value
    video_setting.qmax = 51;
    video_setting.qmin = 0;
    video_setting.quant = FIX_QUANT;
    video_setting.bit_rate = 512;
    video_setting.width = TEST_WIDTH;
    video_setting.height = TEST_HEIGHT;
    video_setting.framerate = 30;
    video_setting.frame_rate_base = 1;
    video_setting.gop_size = 60;
		
    out_virt_buffer = (unsigned int)malloc(video_setting.width*video_setting.height*3/2);	
    h264_init(&video_setting);

    printf("Fix Quant to %d\n",FIX_QUANT);
    
#if 0    //file pattern
    fcount=50;
    while(fcount--)
    {
        fread((void *)enc_mmap_addr,((TEST_WIDTH*TEST_HEIGHT*3)/2),1,din);
        pict.data[0]=(unsigned char *)enc_mmap_addr;
        pict.data[1]=(unsigned char *)(enc_mmap_addr+(video_setting.width*video_setting.height));
        pict.data[2]=(unsigned char *)(enc_mmap_addr+(video_setting.width*video_setting.height)+(video_setting.width*video_setting.height/4));
        msync(enc_mmap_addr,((TEST_WIDTH*TEST_HEIGHT*3)/2),MS_ASYNC);
        length=favc_encode(&video_setting,(unsigned char *)out_virt_buffer,(void *)&pict);
        printf("Length=%d\n",length);fflush(stdout);
        fwrite(out_virt_buffer,1,length,dout);
    }
#else   //performance and long term test 

    fread((void *)enc_mmap_addr,((TEST_WIDTH*TEST_HEIGHT*3)/2),1,din);
    fread((void *)enc_mmap_addr+((TEST_WIDTH*TEST_HEIGHT*3)/2),(TEST_WIDTH*TEST_HEIGHT*3)/2,1,din);

    time_start(&t1);

    tlength=0;
    fcount=1;
    while(1)
    {
        i=TEST_ROUND;
        while(i--)
        {
            int offset;
            if((fcount%20)==0)
            {
                //printf("==>Round %d\n",TEST_ROUND-i);
                nsecs = time_stop(&t1) / 1000;
                printf("[%06d] Spend %06lld ms, %ffps, br=%dKbps\n",fcount,nsecs,(float)((float)fcount)/((float)nsecs/(float)1000.0),((tlength/fcount)*8*30)/1000);
                fflush(stdout);
            }  
            fcount++;
            
            offset=fcount%2;
            pict.data[0]=(unsigned char *)enc_mmap_addr+(((TEST_WIDTH*TEST_HEIGHT*3)/2)*offset);
            pict.data[1]=(unsigned char *)(enc_mmap_addr+(((TEST_WIDTH*TEST_HEIGHT*3)/2)*offset)+(video_setting.width*video_setting.height));
            pict.data[2]=(unsigned char *)(enc_mmap_addr+(((TEST_WIDTH*TEST_HEIGHT*3)/2)*offset)+(video_setting.width*video_setting.height)+(video_setting.width*video_setting.height/4));
    	
            length=favc_encode(&video_setting,(unsigned char *)out_virt_buffer,(void *)&pict);
            tlength+=length;
        }
    }
    
    nsecs = time_stop(&t1) / 1000;
    printf("[%06d] Spend %06lld ms, %ffps, br=%dKbps\n",fcount,nsecs,(float)((float)fcount)/((float)nsecs/(float)1000.0),((tlength/fcount)*8*30)/1000);
    fflush(stdout);
#endif


	fclose(din);
	fclose(dout);
	enc_close(&video_setting);
	free((void *)out_virt_buffer);	
	return 0;
}
